{
 "cells": [
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:33:17.725566Z",
     "start_time": "2025-01-17T11:33:17.692550Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 4
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 加载数据"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:33:25.586560Z",
     "start_time": "2025-01-17T11:33:25.540485Z"
    }
   },
   "source": [
    "from torchvision import datasets\n",
    "from torchvision.transforms import ToTensor\n",
    "\n",
    "# fashion_mnist图像分类数据集\n",
    "train_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=True,\n",
    "    download=True,\n",
    "    transform=ToTensor()\n",
    ")\n",
    "\n",
    "test_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=False,\n",
    "    download=True,\n",
    "    transform=ToTensor()\n",
    ")\n",
    "\n",
    "# torchvision 数据集里没有提供训练集和验证集的划分\n",
    "# 当然也可以用 torch.utils.data.Dataset 实现人为划分\n",
    "# 从数据集到dataloader\n",
    "train_loader = torch.utils.data.DataLoader(train_ds, batch_size=16, shuffle=True)\n",
    "val_loader = torch.utils.data.DataLoader(test_ds, batch_size=16, shuffle=False)"
   ],
   "outputs": [],
   "execution_count": 6
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:33:36.601404Z",
     "start_time": "2025-01-17T11:33:28.736290Z"
    }
   },
   "source": [
    "from torchvision.transforms import Normalize\n",
    "\n",
    "# 遍历train_ds得到每张图片，计算每个通道的均值和方差\n",
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds: # 遍历每张图片,img.shape=[1,28,28]\n",
    "        mean += img.mean(dim=(1, 2))\n",
    "        std += img.std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "\n",
    "print(cal_mean_std(train_ds))\n",
    "# 0.2860， 0.3205\n",
    "transforms = nn.Sequential(\n",
    "    Normalize([0.2860], [0.3205]) # 这里的均值和标准差是通过train_ds计算得到的\n",
    ")\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([0.2860]), tensor([0.3205]))\n"
     ]
    }
   ],
   "execution_count": 7
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型\n",
    "\n",
    "这里我们没有用`nn.Linear`的默认初始化，而是采用了xavier均匀分布去初始化全连接层的权重\n",
    "\n",
    "xavier初始化出自论文 《Understanding the difficulty of training deep feedforward neural networks》，适用于使用`tanh`和`sigmoid`激活函数的方法。当然，我们这里的模型采用的是`relu`激活函数，采用He初始化（何凯明初始化）会更加合适。\n",
    "\n",
    "|神经网络层数|初始化方式|early stop at epoch| val_loss | vla_acc|\n",
    "|-|-|-|-|-|\n",
    "|20|默认|\n",
    "|20|xaviier_uniform|\n",
    "|20|he_uniform|\n",
    "|...|\n",
    "\n",
    "He初始化出自论文 《Delving deep into rectifiers: Surpassing human-level performance on ImageNet classification》"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:59:03.718313Z",
     "start_time": "2025-01-17T11:59:03.700303Z"
    }
   },
   "source": [
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self, layers_num=2):\n",
    "        super().__init__()\n",
    "        self.transforms = transforms # 预处理层，标准化\n",
    "        self.flatten = nn.Flatten()\n",
    "        # 多加几层\n",
    "        self.linear_relu_stack = nn.Sequential(  # 第一层\n",
    "            nn.Linear(28 * 28, 100),\n",
    "            nn.ReLU(),\n",
    "        )\n",
    "        # 加19层  使用add_module可以动态地向nn.Sequential中添加层。 add_module(name, module)\n",
    "        for i in range(1, layers_num):\n",
    "            self.linear_relu_stack.add_module(f\"Linear_{i}\", nn.Linear(100, 100))\n",
    "            self.linear_relu_stack.add_module(f\"relu\", nn.ReLU())\n",
    "        # 输出层\n",
    "        self.linear_relu_stack.add_module(\"Output Layer\", nn.Linear(100, 10))\n",
    "        \n",
    "        # 初始化权重\n",
    "        self.init_weights()\n",
    "        \n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层的权重 W\"\"\"\n",
    "        # print('''初始化权重''')\n",
    "        for m in self.modules():  # m 是当前的模块的实例\n",
    "            # print(m)\n",
    "            # print('-'*50)\n",
    "            if isinstance(m, nn.Linear):#判断m是否为全连接层\n",
    "                # https://pytorch.org/docs/stable/nn.init.html\n",
    "                nn.init.xavier_uniform_(m.weight) # xavier 均匀分布初始化权重\n",
    "                nn.init.zeros_(m.bias) # 全零初始化偏置项\n",
    "        # print('''初始化权重完成''')\n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 1, 28, 28]\n",
    "        x = self.transforms(x) #标准化\n",
    "        x = self.flatten(x)  \n",
    "        # 展平后 x.shape [batch size, 28 * 28]\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        # logits.shape [batch size, 10]\n",
    "        return logits\n",
    "total=0\n",
    "for idx, (key, value) in enumerate(NeuralNetwork(20).named_parameters()):\n",
    "    print(f\"Linear_{idx // 2:>02}\\tparamerters num: {np.prod(value.shape)}\") #np.prod是计算张量的元素个数\n",
    "    # 假设每一层包含一个权重和一个偏置，因此 idx // 2 可以将权重和偏置归为同一层。例如：当 idx = 0 时，idx // 2 = 0（第一层的权重）。当 idx = 1 时，idx // 2 = 0（第一层的偏置）。当 idx = 2 时，idx // 2 = 1（第二层的权重）。当 idx = 3 时，idx // 2 = 1（第二层的偏置）。   :>02 是一个格式化选项，表示将索引右对齐并填充为 2 位数字\n",
    "    print(f\"Linear_{idx // 2:>02}\\tshape: {value.shape}\")\n",
    "    total+=np.prod(value.shape)\n",
    "total #模型参数数量"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Linear_00\tparamerters num: 78400\n",
      "Linear_00\tshape: torch.Size([100, 784])\n",
      "Linear_00\tparamerters num: 100\n",
      "Linear_00\tshape: torch.Size([100])\n",
      "Linear_01\tparamerters num: 10000\n",
      "Linear_01\tshape: torch.Size([100, 100])\n",
      "Linear_01\tparamerters num: 100\n",
      "Linear_01\tshape: torch.Size([100])\n",
      "Linear_02\tparamerters num: 10000\n",
      "Linear_02\tshape: torch.Size([100, 100])\n",
      "Linear_02\tparamerters num: 100\n",
      "Linear_02\tshape: torch.Size([100])\n",
      "Linear_03\tparamerters num: 10000\n",
      "Linear_03\tshape: torch.Size([100, 100])\n",
      "Linear_03\tparamerters num: 100\n",
      "Linear_03\tshape: torch.Size([100])\n",
      "Linear_04\tparamerters num: 10000\n",
      "Linear_04\tshape: torch.Size([100, 100])\n",
      "Linear_04\tparamerters num: 100\n",
      "Linear_04\tshape: torch.Size([100])\n",
      "Linear_05\tparamerters num: 10000\n",
      "Linear_05\tshape: torch.Size([100, 100])\n",
      "Linear_05\tparamerters num: 100\n",
      "Linear_05\tshape: torch.Size([100])\n",
      "Linear_06\tparamerters num: 10000\n",
      "Linear_06\tshape: torch.Size([100, 100])\n",
      "Linear_06\tparamerters num: 100\n",
      "Linear_06\tshape: torch.Size([100])\n",
      "Linear_07\tparamerters num: 10000\n",
      "Linear_07\tshape: torch.Size([100, 100])\n",
      "Linear_07\tparamerters num: 100\n",
      "Linear_07\tshape: torch.Size([100])\n",
      "Linear_08\tparamerters num: 10000\n",
      "Linear_08\tshape: torch.Size([100, 100])\n",
      "Linear_08\tparamerters num: 100\n",
      "Linear_08\tshape: torch.Size([100])\n",
      "Linear_09\tparamerters num: 10000\n",
      "Linear_09\tshape: torch.Size([100, 100])\n",
      "Linear_09\tparamerters num: 100\n",
      "Linear_09\tshape: torch.Size([100])\n",
      "Linear_10\tparamerters num: 10000\n",
      "Linear_10\tshape: torch.Size([100, 100])\n",
      "Linear_10\tparamerters num: 100\n",
      "Linear_10\tshape: torch.Size([100])\n",
      "Linear_11\tparamerters num: 10000\n",
      "Linear_11\tshape: torch.Size([100, 100])\n",
      "Linear_11\tparamerters num: 100\n",
      "Linear_11\tshape: torch.Size([100])\n",
      "Linear_12\tparamerters num: 10000\n",
      "Linear_12\tshape: torch.Size([100, 100])\n",
      "Linear_12\tparamerters num: 100\n",
      "Linear_12\tshape: torch.Size([100])\n",
      "Linear_13\tparamerters num: 10000\n",
      "Linear_13\tshape: torch.Size([100, 100])\n",
      "Linear_13\tparamerters num: 100\n",
      "Linear_13\tshape: torch.Size([100])\n",
      "Linear_14\tparamerters num: 10000\n",
      "Linear_14\tshape: torch.Size([100, 100])\n",
      "Linear_14\tparamerters num: 100\n",
      "Linear_14\tshape: torch.Size([100])\n",
      "Linear_15\tparamerters num: 10000\n",
      "Linear_15\tshape: torch.Size([100, 100])\n",
      "Linear_15\tparamerters num: 100\n",
      "Linear_15\tshape: torch.Size([100])\n",
      "Linear_16\tparamerters num: 10000\n",
      "Linear_16\tshape: torch.Size([100, 100])\n",
      "Linear_16\tparamerters num: 100\n",
      "Linear_16\tshape: torch.Size([100])\n",
      "Linear_17\tparamerters num: 10000\n",
      "Linear_17\tshape: torch.Size([100, 100])\n",
      "Linear_17\tparamerters num: 100\n",
      "Linear_17\tshape: torch.Size([100])\n",
      "Linear_18\tparamerters num: 10000\n",
      "Linear_18\tshape: torch.Size([100, 100])\n",
      "Linear_18\tparamerters num: 100\n",
      "Linear_18\tshape: torch.Size([100])\n",
      "Linear_19\tparamerters num: 10000\n",
      "Linear_19\tshape: torch.Size([100, 100])\n",
      "Linear_19\tparamerters num: 100\n",
      "Linear_19\tshape: torch.Size([100])\n",
      "Linear_20\tparamerters num: 1000\n",
      "Linear_20\tshape: torch.Size([10, 100])\n",
      "Linear_20\tparamerters num: 10\n",
      "Linear_20\tshape: torch.Size([10])\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "271410"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 11
  },
  {
   "cell_type": "code",
   "source": [
    "w = torch.empty(3, 5)\n",
    "print(w)\n",
    "nn.init.eye_(w)   #权重矩阵初始化为单位矩阵"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-17T12:00:39.553530Z",
     "start_time": "2025-01-17T12:00:39.546576Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[-5.4070e+16,  1.4461e-42,  0.0000e+00,  0.0000e+00,  0.0000e+00],\n",
      "        [ 0.0000e+00,  0.0000e+00,  0.0000e+00,  0.0000e+00,  0.0000e+00],\n",
      "        [ 6.6667e-02,  7.4902e-01,  6.2353e-01,  3.0980e-01,  3.3333e-01]])\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "tensor([[1., 0., 0., 0., 0.],\n",
       "        [0., 1., 0., 0., 0.],\n",
       "        [0., 0., 1., 0., 0.]])"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 14
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:01:33.515731Z",
     "start_time": "2025-01-17T12:01:32.517109Z"
    }
   },
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "        preds = logits.argmax(axis=-1)    # 验证集预测\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "        \n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "    return np.mean(loss_list), acc\n"
   ],
   "outputs": [],
   "execution_count": 15
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:03:37.468408Z",
     "start_time": "2025-01-17T12:03:05.401093Z"
    }
   },
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "        \n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\", \n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            )\n",
    "        \n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "        \n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "            \n",
    "        )\n",
    "    \n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)\n"
   ],
   "outputs": [],
   "execution_count": 16
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:03:40.867152Z",
     "start_time": "2025-01-17T12:03:40.862014Z"
    }
   },
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch. \n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = -1\n",
    "        \n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "        \n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "        \n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "\n"
   ],
   "outputs": [],
   "execution_count": 18
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:03:44.461735Z",
     "start_time": "2025-01-17T12:03:44.457850Z"
    }
   },
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ],
   "outputs": [],
   "execution_count": 19
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:03:55.043077Z",
     "start_time": "2025-01-17T12:03:55.032579Z"
    }
   },
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                preds = logits.argmax(axis=-1)\n",
    "            \n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())    \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "                    \n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step, \n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                            )\n",
    "                    \n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=val_acc)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(val_acc)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 100\n",
    "\n",
    "model = NeuralNetwork(layers_num=10)\n"
   ],
   "outputs": [],
   "execution_count": 20
  },
  {
   "cell_type": "code",
   "source": [
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "tensorboard_callback = TensorBoardCallback(\"runs\")\n",
    "tensorboard_callback.draw_model(model, [1, 28, 28])\n",
    "# 2. save best\n",
    "save_ckpt_callback = SaveCheckpointsCallback(\"checkpoints\", save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=0.001)\n",
    "\n",
    "model = model.to(device)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-17T12:04:03.577323Z",
     "start_time": "2025-01-17T12:04:01.695Z"
    }
   },
   "outputs": [],
   "execution_count": 21
  },
  {
   "cell_type": "code",
   "source": [
    "record = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-17T12:18:32.967051Z",
     "start_time": "2025-01-17T12:04:09.347034Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/375000 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "77024facfbf84e62a0227358b7ec523f"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 40 / global_step 150000\n"
     ]
    }
   ],
   "execution_count": 22
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:18:45.553563Z",
     "start_time": "2025-01-17T12:18:44.541746Z"
    }
   },
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(6 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):    \n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "    \n",
    "    plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=10000)  #横坐标是 steps"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 1200x500 with 2 Axes>"
      ],
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAA9UAAAHACAYAAACszkseAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAq55JREFUeJzs3Qd0VFXXBuA3mUkvJAGS0HvvXUARlSIoihWxYEVRsGHFz4Z+ghUrYvsQG4IN9ZcuiEjvSO8dAiEJ6WUyM//a584Nk5AySWaSKe+z1l1TMuWetDv77n328bNarVYQERERERERUbn5l/8pRERERERERCQYVBMRERERERFVEINqIiIiIiIiogpiUE1ERERERERUQQyqiYiIiIiIiCqIQTURERERERFRBTGoJiIiIiIiIqogBtVEREREREREFWSEB7BYLDh58iQiIiLg5+dX3btDREQ+zmq1Ij09HXXr1oW/P89POwOP9URE5KnHe48IquUg26BBg+reDSIiokKOHTuG+vXrV/dueAUe64mIyFOP9x4RVMtZa30wkZGRlXotk8mERYsWYdCgQQgICIAn41jclzeNh2NxT940Fk8cT1pamgoA9eMTVR6P9d4/Fm8bD8finrxpLN42HpMHjsXR471HBNV6GZgcZJ1xoA0NDVWv4yk/zJJwLO7Lm8bDsbgnbxqLJ4+HZcrOw2O994/F28bDsbgnbxqLt43H5MFjKet4z4lgRERERERERBXEoJqIiIiIiIioghhUExEREREREVWQR8ypJiLytOUX8vPzYTabXTovyWg0Iicnx6XvU1XccTwGg0HtE+dNExERUWkYVBMROVFeXh5OnTqFrKwslwfu8fHxqlOyNwR97joeaahSp04dBAYGVveuEBERkTcE1dOmTVPb4cOH1e127drhxRdfxJAhQ0p8zo8//ogXXnhBPadFixZ44403MHTo0MrvORGRm7FYLDh06JDKcNatW1cFYq4KEOW9MjIyEB4eDn9/z5/J427jkSBfTpAkJiaqn6kcv9xhv4iIiMjDg2pZ8Pr1119XHy7kA8dXX32Fa6+9Fps3b1YBdlGrVq3CyJEjMXnyZFx99dWYOXMmhg8fjk2bNqF9+/bOHAcRUbWTIEyCQ1nPUDKcriTvI+8XHBzsFcGeO44nJCRELflx5MiRgn0jIiIiKqpcn1yGDRumsswS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DqpHjBihOq7JRHFpNta5c2csWLCgoBmZLApuv4aaTEaXtamff/55NflcuslJ52+uUU1EREREROS5IiIi1ObrKtSobNy4cWorzrJlyy64T9qhy0ZERERERETkTZy3gjgREREREZGPsVqt1b0LVM0/PwbVRERERERE5aTPGc7KyqruXaFK0H9+lZkD7pbrVBMREREREbkzWWo4KioKZ86cUbdDQ0Ph5+fn1PeQlZby8vKQk5NTqG+VJ7K42VgkQy0Btfz85OcoP8+KYlBNRERERERUAfHx8epSD6xdEfhlZ2cjJCTE6QF7VbO66VgkoNZ/jhXFoJqIiIiIiKgCJDisU6cOYmNjYTKZnP768prLly9Hv379PH6JKpMbjkX2ozIZah2DaiIiIiIiokqQwMwZwVlxr5ufn4/g4GC3CUQryuBFYymq+ovZiYiIiIiIiDwUg2oiIiIiIiKiCmJQTUREREREROTNc6r1BbnT0tKcMkFeWqfLa3l6LT/H4r68aTwci3vyprF44nj045F+fKLK47He+8fibePhWNyTN43F28Zj8sCxOHq894igOj09XV02aNCguneFiIio0PGpRo0a1b0bXoHHeiIi8tTjvZ/VA06zy0LhJ0+eRERERKXXNJOzDXLAPnbsGCIjI+HJOBb35U3j4VjckzeNxRPHI4dOOcDWrVsX/v6cSeUMPNZ7/1i8bTwci3vyprF423jSPHAsjh7vPSJTLQOoX7++U19TfpCe8sMsC8fivrxpPByLe/KmsXjaeJihdi4e631nLN42Ho7FPXnTWLxtPJEeNhZHjvc8vU5ERERERERUQQyqiYiIiIiIiCrI54LqoKAgvPTSS+rS03Es7subxsOxuCdvGos3joeqlzf9PnnTWLxtPByLe/KmsXjbeIK8aCwe2aiMiIiIiIiIyB35XKaaiIiIiIiIyFkYVBMRERERERFVEINqIiIiIiIiogpiUE1ERERERERUQT4VVE+dOhWNGzdGcHAwevXqhXXr1lXp+0+ePBk9evRAREQEYmNjMXz4cOzZs6fQY3JycjB27FjUrFkT4eHhuOGGG3D69OlCjzl69CiuuuoqhIaGqtd56qmnkJ+fX+gxy5YtQ9euXVV3vebNm2PGjBku/X68/vrr8PPzw2OPPeaxYzlx4gRuv/12tb8hISHo0KEDNmzYUPB16en34osvok6d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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 23
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T12:18:49.903783Z",
     "start_time": "2025-01-17T12:18:48.044640Z"
    }
   },
   "source": [
    "# dataload for evaluating\n",
    "\n",
    "# load checkpoints\n",
    "model.load_state_dict(torch.load(\"checkpoints/best.ckpt\", weights_only=True,map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.4810\n",
      "accuracy: 0.8835\n"
     ]
    }
   ],
   "execution_count": 24
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pytorch",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.8"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
